// Initialization Manager
//
// These functions handle creating and initializing all the object, locks, threads, and data object
// for the movie theater simulation
//

#include "utility.h"
#include "copyright.h"
#include "system.h"
#include <sstream>
#include "globalsExtern.cc"
#include "InitializationManager.h"
#include <stdio.h>
#include <iostream.h>
#include <stdlib.h>
#include <time.h>
#ifdef CHANGED
#include "synch.h"
#endif

#include "TicketClerkAgent.h"
#include "HeadCustomerAgent.h"
#include "CustomerAgent.h"
#include "FoodClerkAgent.h"
#include "TicketTakerAgent.h"
#include "ManagerAgent.h"
#include "MovieTechAgent.h"

extern void TicketClerkAgentExecute(int arg);
extern void HeadCustomerAgentExecute(int arg);
extern void CustomerAgentExecute(int arg);
extern void FoodClerkAgentExecute(int arg);
extern void TicketTakerAgentExecute(int arg);
extern void ManagerAgentExecute(int arg);
extern void MovieTechAgentExecute(int arg);

// temp init variables
int numTicketClerks = 2;
int numFoodClerks = 2;
int numCustomerGroups = 8;
int numTicketTakers = 2;

// begin initialization
void InitializeTheaterSimulation() {
	DEBUG('I', "Starting initizalization of theater...\n");
	
	// Initialize debugg printing flags
	// Currently Defined DEBUG flags:
	// 'I' = initialization
	// 'L' = Locks
	// 'C' = Condition Variables
	// 'H' = Head Customer Agent thread
	// 'T' = Ticket Clerk Agent thread
	// 'F' = Food Clerk Agent thread
	// 'c' = Customer Agent thread
	// 'A' = Ticket Taker Agent thread
	// 'M' = Manager thread
	// 'R' = Required output thread
	// 'D' = Dale's personal debug
	// 'm' = Movie Tech thread
	//DebugInit("MITmR");
	DebugInit("R");
	
	//Asks user to determine the number of clerks and customer groups
	//If an input is given that was not an integer, it will loop endlessly
	cout << "How many ticket clerks? (Between 1 and 5)\n";
	cin >> numTicketClerks;
	while (numTicketClerks < 1 || numTicketClerks >5){
		cout << "Incorrect number! How many ticket clerks? (Between 1 and 5)\n";
		cin >> numTicketClerks;
	}
	cout << "How many food clerks? (Between 1 and 5)\n";
	cin >> numFoodClerks;
	while (numFoodClerks < 1 || numFoodClerks >5){
		cout << "Incorrect number! How many food clerks? (Between 1 and 5)\n";
		cin >> numFoodClerks;
	}
	cout << "How many ticket takers? (Between 1 and 5)\n";
	cin >> numTicketTakers;
	while (numTicketTakers < 1 || numTicketTakers >5){
		cout << "Incorrect number! How many ticket takers? (Between 1 and 5)\n";
		cin >> numTicketTakers;
	}
	cout << "How many customer groups? (Between 1 and 40)\n";
	cin >> numCustomerGroups;
	while (numCustomerGroups < 1 || numCustomerGroups >40){
		cout << "Incorrect number! How many customer groups? (Between 1 and 40)\n";
		cin >> numCustomerGroups;
	}
	
	initializeGlobals();
	initializeSynchronization();
	initializeThreads();
}

// Initialize all the test cases needed for our test suite
void InitializeTheaterTests() {
	DebugInit("RI");

	DEBUG('I', "Starting initizalization of theater tests...\n");
	
	DEBUG('I', "Starting test 1...\n");
	
	ticketLineSelectionWhiteboard = new LineSelectionWhiteboard(2);
	ticketLineSelectionLock			= new Lock("ticketLineSelectionLock");
	ticketLineSelectionCondition[0]	= new Condition("ticketLineSelectionCondition");
	ticketLineSelectionCondition[1]	= new Condition("ticketLineSelectionCondition");
	
	char* name;
	Thread *t;
	
	// Fork 3 HeadCustomer threads for this test
	t = new Thread("Head Customer 0 in Group 1");
	t->Fork((VoidFunctionPtr)HeadCustomerAgentExecute,1);
	
	t = new Thread("Head Customer 0 in Group 2");
	t->Fork((VoidFunctionPtr)HeadCustomerAgentExecute,2);
	
	t = new Thread("Head Customer 0 in Group 3");
	t->Fork((VoidFunctionPtr)HeadCustomerAgentExecute,3);
	
	// Expected result:
	// Group 1 and 3 went to line 0
	// Group 2 went to line 1
	
	DEBUG('I', "\nStarting test 2...\n");
	// In our simulation all our clerks aggregate their sales information within a lock
	// so the manager has access to all of that information at once.
	
	DEBUG('I', "\nStarting test 3...\n");
	
	ticketLineSelectionWhiteboard 	= new LineSelectionWhiteboard(1);
	ticketClerkCustomerWhiteboard[0]= new TicketClerkCustomerWhiteboard();
	ticketClerkCustomerLock[0]		= new Lock("ticketClerkCustomerLock");
	ticketLineSelectionLock			= new Lock("ticketLineSelectionLock");
	ticketClerkCondition[0]			= new Condition("ticketClerkCondition");
	ticketCustomerCondition[0]		= new Condition("ticketCustomerCondition");
	ticketLineSelectionCondition[0]	= new Condition("ticketLineSelectionCondition");
	groupWhiteboard[0]				= new GroupWhiteboard();
	groupWhiteboard[0]->setGroupSize(1);
	groupWhiteboard[1]				= new GroupWhiteboard();
	groupWhiteboard[1]->setGroupSize(1);
	groupWhiteboard[2]				= new GroupWhiteboard();
	groupWhiteboard[2]->setGroupSize(1);
	
	t = new Thread("Ticket Clerk 0");
	t->Fork((VoidFunctionPtr)TicketClerkAgentExecute,0);
	
	// Fork 3 HeadCustomer threads for this test
	t = new Thread("Head Customer 0 in Group 0");
	t->Fork((VoidFunctionPtr)HeadCustomerAgentExecute,0);
	
	t = new Thread("Head Customer 0 in Group 1");
	t->Fork((VoidFunctionPtr)HeadCustomerAgentExecute,1);
	
	t = new Thread("Head Customer 0 in Group 2");
	t->Fork((VoidFunctionPtr)HeadCustomerAgentExecute,2);
	
	
	// Expected output:
	// Group 1 does not leave until a ticket is printed for them.
	// Group 2 is not called to the clerk until Group 1 has left.
	
	DEBUG('I', "\nStarting test 4...\n");
	
	ticketLineSelectionWhiteboard 		= new LineSelectionWhiteboard(2);
	ticketClerkCustomerWhiteboard[0]	= new TicketClerkCustomerWhiteboard();
	ticketClerkCustomerWhiteboard[1]	= new TicketClerkCustomerWhiteboard();
	managerWhiteboard					= new ManagerWhiteboard(2, 1, 1);
	ticketLineSelectionCondition[0]		= new Condition("ticketLineSelectionCondition0");
	ticketLineSelectionCondition[1]		= new Condition("ticketLineSelectionCondition1");
	ticketClerkCustomerLock[0]			= new Lock("ticketClerkCustomerLock");
	ticketClerkCustomerLock[1]			= new Lock("ticketClerkCustomerLock");
	managerLock							= new Lock("managerLock");
	ticketCustomerCondition[0]			= new Condition("ticketCustomerCondition");
	ticketCustomerCondition[1]			= new Condition("ticketCustomerCondition");
	ticketClerkCondition[0]				= new Condition("ticketClerkCondition");
	ticketClerkCondition[1]				= new Condition("ticketClerkCondition");
	managerCondition					= new Condition("managerCondition");
	
	managerWhiteboard->setTicketClerkStatus(1, managerWhiteboard->CLERK_BREAK);
	ticketLineSelectionWhiteboard->setLineOpen(1, false);
	
	t = new Thread("Ticket Clerk 0");
	t->Fork((VoidFunctionPtr)TicketClerkAgentExecute,0);
	
	t = new Thread("Ticket Clerk 1");
	t->Fork((VoidFunctionPtr)TicketClerkAgentExecute,1);
	
	t = new Thread("Manager");
	t->Fork((VoidFunctionPtr)ManagerAgentExecute,0);
	
	for (int j = 0; j < 7; j++) {
		//int size = rand() % 5;
		int size = 3;
		groupWhiteboard[j] = new GroupWhiteboard();
		groupWhiteboard[j]->setGroupSize(3);
		groupLock[j]		= new Lock("GroupLock");
		headCustomerCondition[j] = new Condition("headCustomerCondition");
		groupCustomerCondition[j] = new Condition("groupCustomerCondition");
		groupWhiteboard[j]->setGroupSize(size+1);
	
		// Fork all customer threads in a group
		for (int i = 0; i < size; i++) {
			name = new char [40];
			sprintf(name, "Customer %d in Group %d", i+1,j);
			t = new Thread(name);
			t->Fork((VoidFunctionPtr)CustomerAgentExecute,j);
		}

		// Fork all HeadCustomer threads
		name = new char [40];
		sprintf(name, "Head Customer 0 in Group %d", j);
		t = new Thread(name);
		t->Fork((VoidFunctionPtr)HeadCustomerAgentExecute,j);
	}
	
	// Expected result:
	// All customers go to line 0 (since line 1 is closed)
	// After the first customer the manager notices that a line is longer than 5
	// and opens line 1 by bringing clerk 1 off of break.
	
	DEBUG('I', "\nStarting test 5...\n");
	// Do not know how to test for this
	
	DEBUG('I', "\nStarting test 6...\n");
	// Groups follow their head customer in our simulation
	
}

// initialize global data
// This will be all whiteboard objects and global data
// objects what will be used by the threads
void initializeGlobals() {
	DEBUG('I', "Initializing globals...\n");
	
	char* name;
	
	ticketClerkCustomerWhiteboard[numTicketClerks];
	for (int i = 0; i < numTicketClerks; i++) {
		ticketClerkCustomerWhiteboard[i] = new TicketClerkCustomerWhiteboard();
	}
	
	foodClerkCustomerWhiteboard[numFoodClerks];
	for (int i = 0; i < numFoodClerks; i++) {
		foodClerkCustomerWhiteboard[i] = new FoodClerkCustomerWhiteboard();
	}
	
	ticketTakerCustomerWhiteboard[numTicketTakers];
	for (int i = 0; i < numTicketTakers; i++) {
		ticketTakerCustomerWhiteboard[i] = new TicketTakerCustomerWhiteboard();
	}
	
	for (int i = 0; i < numCustomerGroups; i++) {
		groupWhiteboard[i]				  	= new GroupWhiteboard();
	}
	ticketLineSelectionWhiteboard 	= new LineSelectionWhiteboard(numTicketClerks);
	foodLineSelectionWhiteboard		= new LineSelectionWhiteboard(numFoodClerks);
	totalsWhiteboard 				= new TotalsWhiteboard();
	takerLineSelectionWhiteboard 	= new LineSelectionWhiteboard(numTicketTakers);
	theaterSelectionWhiteboard		= new TheaterSelectionWhiteboard();
	managerWhiteboard				= new ManagerWhiteboard(numTicketClerks, numFoodClerks, numTicketTakers);
}

// initialize all monitors, condition variables, and other synchronization machinery here.
void initializeSynchronization() {
	DEBUG('I', "Initializing synchronization objects...\n");
	char* name;
	
	for (int i = 0; i < numTicketClerks; i++) {
		name = new char [20];
		sprintf(name, "TicketClerkCustomerLock %d", i);
		ticketClerkCustomerLock[i] 	= new Lock(name);
		
		name = new char [20];
		sprintf(name, "TicketClerkCondition %d", i);
		ticketClerkCondition[i] 		= new Condition(name);
		
		name = new char [20];
		sprintf(name, "CustomerCondition %d", i);
		ticketCustomerCondition[i]	= new Condition(name);
		
		name = new char [20];
		sprintf(name, "TicketLineSelectionCondition %d", i);
		ticketLineSelectionCondition[i] 	= new Condition(name);
	}
	
	for (int i = 0; i < numFoodClerks; i++) {
		name = new char [20];
		sprintf(name, "FoodClerkCustomerLock %d", i);
		foodClerkCustomerLock[i] 	= new Lock(name);
		
		name = new char [20];
		sprintf(name, "FoodClerkCondition %d", i);
		foodClerkCondition[i] 		= new Condition(name);
		
		name = new char [20];
		sprintf(name, "CustomerCondition %d", i);
		foodCustomerCondition[i]	= new Condition(name);
		
		name = new char [20];
		sprintf(name, "FoodLineSelectionCondition %d", i);
		foodLineSelectionCondition[i] 	= new Condition(name);
	}
	
	for (int i = 0; i < numTicketTakers; i++) {
		name = new char [20];
		sprintf(name, "TicketTakerCustomerLock %d", i);
		ticketTakerCustomerLock[i] 	= new Lock(name);
		
		name = new char [20];
		sprintf(name, "TicketTakerCondition %d", i);
		ticketTakerCondition[i] 		= new Condition(name);
		
		name = new char [20];
		sprintf(name, "CustomerCondition %d", i);
		takerCustomerCondition[i]	= new Condition(name);
		
		name = new char [20];
		sprintf(name, "TakerLineSelectionCondition %d", i);
		takerLineSelectionCondition[i] 	= new Condition(name);
	}
	
	for (int i = 0; i < numCustomerGroups; i++) {
		name = new char [20];
		sprintf(name, "GroupLock %d", i);
		groupLock[i]	= new Lock(name);
		
		name = new char [20];
		sprintf(name, "HeadCustomerCondition %d", i);
		headCustomerCondition[i]	= new Condition(name);
		
		name = new char [20];
		sprintf(name, "GroupCustomerCondition %d", i);
		groupCustomerCondition[i] 	= new Condition(name);
	}
	
	totalsLock					= new Lock("TotalsLock");;
	ticketLineSelectionLock 	= new Lock("TicketLineSelectionLock");
	foodLineSelectionLock		= new Lock("FoodLineSelectionLock");
	takerLineSelectionLock		= new Lock("TakerLineSelectionLock");
	theaterSelectionLock		= new Lock("TheaterSelectionLock");
	managerLock					= new Lock("ManagerLock");
	movieTechLock				= new Lock("MovieTechLock");
	
	totalsTicketClerkCondition 		= new Condition("TotalsTicketClerkCondition");
	totalsFoodClerkCondition		= new Condition("TotalsFoodClerkCondition");
	managerCondition				= new Condition("ManagerCondition");
	theaterSeatedCustomerCondition	= new Condition("TheaterSeatedCustomerCondition");
	theaterTicketTakerCondition		= new Condition("TheaterTicketTakerCondition");
	movieTechCondition				= new Condition("MovieTechCondition");
}

// initialize and fork all threads here.
void initializeThreads() {
	DEBUG('I', "Initializing threads...\n");
	
	char* name;
	Thread *t;
	
	// Temp test initialization
	srand ( time(NULL) );
	
	// Fork all TicketClerks threads
	DEBUG('R', "Number of TicketClerks = %d.\n",numTicketClerks);
	for (int i = 0; i < numTicketClerks; i++) {
		name = new char [20];
		sprintf(name, "Ticket Clerk %d", i);
		t = new Thread(name);
		t->Fork((VoidFunctionPtr)TicketClerkAgentExecute,i);
	}
	
	// Fork all FoodClerks threads
	DEBUG('R',"Number of FoodClerks = %d.\n",numFoodClerks);
	for (int i = 0; i < numFoodClerks; i++) {
		name = new char [20];
		sprintf(name, "Food Clerk %d", i);
		t = new Thread(name);
		t->Fork((VoidFunctionPtr)FoodClerkAgentExecute,i);
	}
	
	// Fork all TicketTaker threads
	DEBUG('R',"Number of TicketTakers = %d.\n",numTicketTakers);
	for (int i = 0; i < numTicketTakers; i++) {
		name = new char [20];
		sprintf(name, "Ticket Taker %d", i);
		t = new Thread(name);
		t->Fork((VoidFunctionPtr)TicketTakerAgentExecute,i);
	}
	
	DEBUG('R',"Number of Groups = %d.\n",numCustomerGroups);
	for (int j = 0; j < numCustomerGroups; j++) {
		int size = rand() % 5;
		//int size = 3;
		groupWhiteboard[j]->setGroupSize(size+1);
	
		// Fork all customer threads in a group
		for (int i = 0; i < size; i++) {
			name = new char [40];
			sprintf(name, "Customer %d in Group %d", i+1,j);
			t = new Thread(name);
			t->Fork((VoidFunctionPtr)CustomerAgentExecute,j);
			DEBUG('R', "Customer %d of group %d is waiting for the HeadCustomer.\n",i,j);
		}

		// Fork all HeadCustomer threads
		name = new char [40];
		sprintf(name, "Head Customer 0 in Group %d", j);
		t = new Thread(name);
		t->Fork((VoidFunctionPtr)HeadCustomerAgentExecute,j);
		DEBUG('R', "HeadCustomer %d of group %d has told the group to proceed.\n",0,j);
	}
	
	// Movie Tech thread
	t = new Thread("Movie Tech");
	t->Fork((VoidFunctionPtr)MovieTechAgentExecute,0);
	
	// Fork manager thread
	t = new Thread("Manager");
	t->Fork((VoidFunctionPtr)ManagerAgentExecute,0);
}
